package edu.hawaii.halealohacli.kmj;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
//import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
//import java.util.TimeZone;
//import java.util.Scanner;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.WattDepotClient;

/**
 * Checks user input for format-correctness, then calls appropriate commands. Each command has its
 * own specific method in this class.
 * 
 * @author Richard Kamana Akina
 * 
 */
public class CommandParser {

  /**
   * The WattDepot client used to retrieve energy and power information from the server.
   */
  protected WattDepotClient client;

  /**
   * The Baseline object which stores the current baseline power use data.
   */
  protected Baseline storedBaseline;

  /**
   * Tracks whether storedBaseline is currently set.
   */
  protected boolean isBaselineSet;

  /**
   * Used as part of the error message indicating incorrect date input.
   */
  protected String dateError = "Date";

  /**
   * Constructor for CommandParser.
   * 
   * @param clientname The client to be used
   */
  public CommandParser(WattDepotClient clientname) {
    client = clientname;
  }

  /**
   * Checks the validity of user-input strings, and invokes respective commands if valid. This will
   * be the command that will be called in the main.java.
   * 
   * command user-input format: [command][space][command-specific details] The entire user-input
   * string should be submitted to this method.
   * 
   * First attempts to create a substring containing [command]. If there is no [space] separating
   * [command] from [command-specific details], an error is thrown. If the new [command] subString
   * matches to any of the commands, then that command-specific method is invoked which further
   * checks for string validity. If additional commands are added in the future, this method must be
   * altered as such: -command must be added to String[] commands -switch statement must be added
   * for new command A command-specific method must also be created outside this method.
   * 
   * @param input user-input string to be checked
   * @throws DatatypeConfigurationException If error in configuration.
   */
  public void commandCheck(String input) throws DatatypeConfigurationException {
    final String[] commands =
        { "current-power", "daily-energy", "energy-since", "rank-towers", "set-baseline",
            "monitor-power", "monitor-goal" };
    String smallString;
    int command = 0;
    if ("help".equals(input)) {
      help();
    }
    else {
      try {
        boolean valid = false;
        smallString = input.substring(0, input.indexOf(' '));
        for (int i = 0; i < commands.length; i++) {
          if ((smallString.equalsIgnoreCase(commands[i]))) {
            valid = true;
            command = i;
            break;
          }
        }
        if (valid) {
          switch (command) {
          case 0:
            currentPower(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 1:
            dailyEnergy(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 2:
            energySince(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 3:
            rankTowers(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 4:
            setBaseline(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 5:
            monitorPower(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          case 6:
            monitorGoal(input.substring(input.indexOf(' ') + 1, input.length()));
            break;
          default:
            System.out.println("Command error. Please try again.");
          }
        }
        else {
          errorMessage("Command");
        }
      }
      catch (StringIndexOutOfBoundsException e) {
        errorMessage("Command");
      }
    }
  }

  /**
   * Checks to see if parameter is a valid location. Contains a string array containing all possible
   * locations. If more locations are to be added in the future, then they must be added to this
   * internal array.
   * 
   * @param input string or substring containing location
   * @return String of location if true, null if false
   */
  public String locationCheck(String input) {
    boolean match = false;
    final String[] locations =
        { "Mokihana", "Mokihana-A", "Mokihana-B", "Mokihana-C", "Mokihana-D", "Mokihana-E",
            "Ilima", "Ilima-A", "Ilima-B", "Ilima-C", "Ilima-D", "Ilima-E", "Lehua", "Lehua-A",
            "Lehua-B", "Lehua-C", "Lehua-D", "Lehua-E", "Lokelani", "Lokelani-A", "Lokelani-B",
            "Lokelani-C", "Lokelani-D", "Lokelani-E" };
    for (int i = 0; i < locations.length; i++) {
      if (locations[i].equalsIgnoreCase(input)) {
        match = true;
        return locations[i];
      }
    }
    if (!match) {
      errorMessage("Location");
    }
    return null;

  } // end of locationCheck()

  /**
   * Checks to see if parameter is a correctly-formatted date.
   * 
   * Source of code:
   * http://www.dreamincode.net/forums/topic/14886-date-validation-using-simpledateformat/
   * 
   * @param input string or substring containing date that should be in yyyy-mm-dd format
   * @return String of date if valid, null if false
   */
  public String dateCheck(String input) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    Date testDate = null;
    try {
      testDate = sdf.parse(input);
    }
    catch (ParseException e) {
      errorMessage(dateError);
      return null;
    }
    if (!sdf.format(testDate).equals(input)) {
      errorMessage(dateError);
      return null;
    }
    return input;
  }

  /**
   * Converts date to XMLGregorianCalendar. Adapted from Micah's TestRankTowers.java
   * 
   * @param input the date string
   * @return new XMLGregorianCalendar
   * @throws DatatypeConfigurationException If error in configuration.
   */
  public XMLGregorianCalendar createXMLGC(String input) throws DatatypeConfigurationException {
    int year = Integer.parseInt(input.substring(0, 4));
    int month = Integer.parseInt(input.substring(5, 7));
    int day = Integer.parseInt(input.substring(8, input.length()));

    XMLGregorianCalendar newdate =
        DatatypeFactory.newInstance().newXMLGregorianCalendar(1, 1, 1, 0, 0, 0, 0, 0);
    newdate.setDay(day);
    newdate.setYear(year);
    newdate.setMonth(month);
    return newdate;
  }

  // /COMMAND METHODS///

  /**
   * Checks a section of the user-input for the current-power command.
   * 
   * current-power user-input format: current-power[space][location] Only the [location] substring
   * of the user-input in should be submitted to this method.
   * 
   * If successful, invokes the currentPower command.
   * 
   * @param input the 'location' portion of user-input.
   */
  public void currentPower(String input) {
    String location = locationCheck(input);
    if (location != null) {
      CurrentPower cp = new CurrentPower(client, location);
      cp.printResult();
      // instanceOfCurrentPower.
    }
  }

  /**
   * Checks a section of the user-input for the daily-energy command.
   * 
   * daily-energy user-input format: daily-energy[space][location][space][date] Only the
   * [location][space][date] substring of the user-input should be submitted.
   * 
   * First checks to see if the [space] is present in the string, if not throws an error. Then
   * proceeds to check [location] and [date], and if both are valid, the daily-energy command is
   * invoked.
   * 
   * @param input substring of user-input containing [location][space][date]
   */
  public void dailyEnergy(String input) {
    try {
      String location = locationCheck(input.substring(0, input.indexOf(' ')));
      String date = dateCheck(input.substring(input.indexOf(' ') + 1, input.length()));
      if (location != null && date != null) {
        DailyEnergy de = new DailyEnergy(client, location, date);
        de.printResult();
      }
    }
    catch (StringIndexOutOfBoundsException e) {
      errorMessage("Location or Date");
    }
  }

  /**
   * Checks a section of the user-input for the energySince command.
   * 
   * energy-since user-input format: energy-since[space][location][space][date] Only the
   * [location][space][date] substring of the user-input should be submitted.
   * 
   * First checks to see if the [space] is present in the string, if not throws an error. Then
   * proceeds to check [location] and [date], and if both are valid, the energy-since command is
   * invoked.
   * 
   * @param input substring of user-input containing [location][space][date]
   */
  public void energySince(String input) {
    try {
      String location = locationCheck(input.substring(0, input.indexOf(' ')));
      String date = dateCheck(input.substring(input.indexOf(' ') + 1, input.length()));
      if (location != null && date != null) {
        EnergySince es = new EnergySince(client, location, date);
        es.printResult();
      }
    }
    // Triggered by a missing space.
    catch (StringIndexOutOfBoundsException e) {
      errorMessage("Location or Date ");
    }
  }

  /**
   * Checks a section of the user-input for the rank-towers command.
   * 
   * rank-towers user-input format: rank-towers[space][date][space][date] Only the
   * [date][space][date] substring of the user-input should be read.
   * 
   * checks to see if the space is present in the string, if not throws error. Then proceeds to
   * check both [date] areas, if date1 and date2 are valid, invokes the rank-towers command.
   * 
   * @param input substring of user-input containing [location][space][date]
   * @throws DatatypeConfigurationException If error in configuration.
   */
  public void rankTowers(String input) throws DatatypeConfigurationException {
    try {
      String date1 = dateCheck(input.substring(0, input.indexOf(' ')));
      String date2 = dateCheck(input.substring(input.indexOf(' ') + 1, input.length()));
      if (date1 != null && date2 != null) {
        XMLGregorianCalendar date1xml = createXMLGC(date1);
        XMLGregorianCalendar date2xml = createXMLGC(date2);
        RankTowers rt = new RankTowers(client, date1xml, date2xml);
        rt.printResult();
      }
    }
    catch (StringIndexOutOfBoundsException e) { // missing a space
      errorMessage(dateError);
    }
  }

  /**
   * Checks a section of the user input for the set-baseline command.
   * 
   * set-baseline user-input format: set-baseline[space][location][space][date] Only the
   * [location][space][date] substring of the user-input should be read.
   * 
   * This method first checks if the location is the name of a valid Source, then checks if the date
   * is valid and in yyyy-MM-dd format. It then invokes the SetBaseline constructor.
   * 
   * @param input Substring of user-input in the form [location][space][date]
   */
  public void setBaseline(String input) {
    try {
      // Input checking.
      String[] args = input.split("[ ]++");
      String location = null;
      String date = null;

      if (args.length == 2) {
        location = locationCheck(args[0]);
        date = dateCheck(args[1]);
      }
      else if (args.length == 1) {
        location = locationCheck(args[0]);
      }
      else if (args.length > 2) {
        errorMessage("More than two arguments");
      }

      // A date string is an optional argument; only location matters.
      if (location != null) {
        SetBaseline setBaseline = new SetBaseline(client, location, date);
        this.storedBaseline = setBaseline.getBaselineData();
        this.isBaselineSet = true;
        setBaseline.printResult();
      }
    }
    // Triggered when input is missing a space.
    catch (StringIndexOutOfBoundsException siobe) {
      errorMessage(dateError);
    }
    // Triggered if some other error occurs.
    catch (Exception e) {
      System.out.println("Error: " + e.getMessage());
      System.out.format("Failed to set baseline for input: \"%s\"", input);
    }

  }

  /**
   * Checks a section of the user-input for the monitor-power command.
   * 
   * monitor-power user-input format: monitor-power[space][location] [interval] Only the [location]
   * substring of the user-input in should be submitted to this method.
   * 
   * If successful, invokes the monitorPower command.
   * 
   * @param input the 'location' portion of user-input.
   */
  public void monitorPower(String input) {
    String[] args = input.split("[ ]++");
    String location = "";
    int interval = 0;
    boolean numberError = false;

    try {
      location = locationCheck(args[0]);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      errorMessage("Location");
    }
    try {
      interval = Integer.parseInt(args[1]);
    }
    catch (NumberFormatException e) {
      errorMessage("Number Format");
      numberError = true;
    }
    catch (ArrayIndexOutOfBoundsException e) {
      // default value = 10 seconds
      interval = 10;
    }
    if (interval < 1) {
      errorMessage("Number must be greater than zero ");
      numberError = true;
    }
    if (location != null && !numberError) {
      Timer timer = new Timer();
      int systemIn = 0;
      
      while (systemIn == 0) {
        WattDepotClient now = new WattDepotClient("http://server.wattdepot.org:8190/wattdepot/");
        timer.scheduleAtFixedRate(new RepeatTask(now, location), 0, interval * 1000);
        try {
          systemIn = System.in.read();
        }
        catch (IOException e) {
          System.out.println("ye");
        }
      }
      timer.cancel();
    }
  }

  /**
   * Checks a section of the user-input for the monitor-goal command.
   * 
   * monitor-goal user-input format: monitor-goal[space][location][space][goal][space][interval]
   * Only the [location][space][goal][space][interval] substring of the user-input should be read.
   * 
   * Checks that the proper baseline is set, that the location is a valid source, and that the goal
   * and interval fall within the accepted ranges (1 <= goal <= 99, interval > 0).
   * 
   * Executes the MonitorGoal command if all inputs are valid and the proper baseline is set.
   * 
   * @param input Substring in the form [location][space][goal][space][interval].
   */
  public void monitorGoal(String input) {
    String[] args = input.split("[ ]+");
    if (!isBaselineSet) {
      System.out.println("Baseline must be set with the set-baseline command"
          + " before running monitor-goal.");
      return;
    }

    if (args.length != 3) {
      System.out.println("Expected 3 arguments (found " + args.length + ").");
      return;
    }
    String source = locationCheck(args[0]);

    if (source == null) {
      return;
    }

    if (!source.equals(locationCheck(this.storedBaseline.getSourceName()))) {
      System.out.println("Baseline must be set for the source \"" + source
          + "\" with the the set-baseline command before running monitor-goal.");
      return;
    }
    int goal = 5;
    try {
     goal = Integer.parseInt(args[1]);
    }
    catch (NumberFormatException e) {
      System.out.println("Unable to read % goal.  Defaulting to 5%.");
    }
    
    if (goal > 99 || goal < 1) {
      System.out.println("Goal out of range (1 <= goal <= 99).");
      return;
    }

    int interval = 10;
    try {
      interval = Integer.parseInt(args[2]);
    }
    catch (NumberFormatException e) {
      System.out.println("Unable to read interval.  Defaulting to 10s.");
    }
    if (interval <= 0) {
      System.out.println("Interval out of range (interval > 0).");
      return;
    }

    MonitorGoal command = new MonitorGoal(client, source, goal, interval, storedBaseline);
    command.printResult();
  }

  /**
   * Invokes the help command.
   */
  public void help() {
    System.out.println("Getting help!");
    HelpCommand hc = new HelpCommand();
    hc.printResult();
  }

  /**
   * Prints out error message.
   * 
   * @param input The specific error
   */
  public void errorMessage(String input) {
    System.out.println(input + " was entered in incorrectly."
        + " Please try again, or type in \"help\" for help.");
  }
}
